﻿/// ****************************************************************************
/// Author:	        Robin Zhu
/// Create Date:	2012-4-16
/// Purpose: 		身份验证的数据库实现版本
/// ****************************************************************************
/// Modify By		Date			Remark
/// Robin           2012-6-4        实现AddUserToApp接口
/// Robin           2012-6-5        在删除UserAccount同时删除相关关系表（UserAppEntry和UserAccountRole）中的记录
/// Robin           2013-5-24       FindUsersByName和FindUsersByEmail使用StartWith而不用完全匹配
/// Robin           2014-3-13       修复UpdateUser的Bug
/// ****************************************************************************
/// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Threading;
using RB.DataAccess;
using RB.Domain.RelationalMapping;

namespace RB.Web.Security.Rdb
{
    public class DbMembershipService : IMembershipService
    {
        public bool CreateUser(UserAccount userAccount)
        {
            try
            {
                UserAccountSet accountSet = new UserAccountSet();
                accountSet.Add(userAccount);

                return true;
            }
            catch
            {
                throw;
            }
        }

        public bool DeleteUser(string username, bool deleteAllRelatedData, string deleteBy)
        {
            bool result = false;

            Executor exec = DataContext.CreateExecutor();

            try
            {
                exec.BeginTransaction();
                UserAccountSet accountSet = new UserAccountSet();
                if (deleteAllRelatedData)
                {
                    UserAccount account = accountSet[username];
                    if (account != null)
                    {
                        UserAppEntrySet userAppSet = new UserAppEntrySet();
                        UserAccountRoleSet userRoleSet = new UserAccountRoleSet();

                        userAppSet.RemoveAll(p => p.UserId == account.Id, exec);
                        userRoleSet.RemoveAll(p => p.UserId == account.Id, exec);
                        accountSet.RemoveAll(p => p.Id == account.Id, exec);
                    }
                }
                else
                {
                    accountSet.Update(
                        p => new { IsDisabled = true, ModifyBy = deleteBy, ModifyTime = DateTime.Now },
                        p => p.UserName == username, exec);
                }
                exec.CommitTransaction();

                result = true;
            }
            catch
            {
                exec.RollbackTransaction();

                throw;
            }
            return result;
        }

        public IList<UserAccount> FindUsersByEmail(string emailToMatch)
        {
            if (string.IsNullOrEmpty(emailToMatch))
                return new List<UserAccount>();
            UserAccountSet accountSet = new UserAccountSet();
            return accountSet.Where(p => p.Email.StartsWith(emailToMatch) && !(bool)p.IsDisabled).ToList();
        }

        public IList<UserAccount> FindUsersByName(string usernameToMatch)
        {
            if (string.IsNullOrEmpty(usernameToMatch))
                return new List<UserAccount>();
            UserAccountSet accountSet = new UserAccountSet();
            return accountSet.Where(p => (p.UserName.StartsWith(usernameToMatch) || p.FirstName.StartsWith(usernameToMatch) || p.LastName.StartsWith(usernameToMatch)) && !(bool)p.IsDisabled).ToList();
        }

        public IList<UserAccount> GetAllUsers()
        {
            UserAccountSet accountSet = new UserAccountSet();
            return accountSet.Where(p => !(bool)p.IsDisabled).ToList();
        }

        public int GetNumberOfUsersOnline(int userIsOnlineTimeWindow)
        {
            UserAccountSet accountSet = new UserAccountSet();
            var list = accountSet.Where(p => !(bool)p.IsDisabled && p.IsApproved).ToList();
            DateTime now = DateTime.Now;
            return list.Count(p => (now - (p.LastActivityDate ?? DateTime.MinValue)).TotalMinutes < userIsOnlineTimeWindow);
        }

        public UserAccount GetUserByName(string username, bool userIsOnline)
        {
            UserAccountSet accountSet = new UserAccountSet();
            UserAccount result = accountSet[username];
            if (userIsOnline)
            {
                accountSet.Update(p => new { LastActivityDate = DateTime.Now }, p => p.Id == result.Id);
            }
            return result;
        }

        public UserAccount GetUserByKey(Guid providerUserKey, bool userIsOnline)
        {
            UserAccountSet accountSet = new UserAccountSet();
            UserAccount result = accountSet.AsQueryable().Where(p => p.Id == (Guid)providerUserKey).FirstOrDefault();
            if (userIsOnline)
            {
                accountSet.Update(p => new { LastActivityDate = DateTime.Now }, p => p.Id == result.Id);
            }
            return result;
        }

        public UserAccount GetUserByEmail(string email)
        {
            UserAccountSet accountSet = new UserAccountSet();
            UserAccount result = accountSet.Where(p => p.Email == email).FirstOrDefault();

            return result;
        }

        public bool UnlockUser(string userName)
        {
            UserAccountSet accountSet = new UserAccountSet();

            int count = accountSet.Update(p => new { IsLockedOut = false }, p => p.UserName == userName);

            return count > 0;
        }

        public void UpdateUser(UserAccount user)
        {
            UserAccountSet accountSet = new UserAccountSet();

            accountSet.Update(
                p => new
                {
                    UserName = user.UserName,
                    Email = user.Email,
                    FirstName = user.FirstName,
                    LastName = user.LastName,
                    ModifyBy = user.ModifyBy,
                    ModifyTime = user.ModifyTime,
                    IsStaff = user.IsStaff,
                    IsApproved = user.IsApproved,
                    IsLockedOut = user.IsLockedOut
                },
                p => p.Id == user.Id);
        }

        public bool LoginUser(string username, string password, int maxInvalidPasswordAttempts, int passwordAttemptWindow)
        {
            UserAccountSet accountSet = new UserAccountSet();
            UserAccount account = accountSet.GetLogonUser(username);
            if (account != null)
            {
                /// 当当前时间距离上次锁定时间的分钟数大于密码尝试窗口分钟数设定，则自动解锁
                if (account.IsLockedOut 
                    && (DateTime.Now - (account.LastLockoutDate ?? DateTime.MinValue)).TotalMinutes > passwordAttemptWindow)
                {
                    account.IsLockedOut = false;
                }
                if (!account.IsLockedOut)
                {
                    if (account.Password == password)
                    {
                        accountSet.Update(
                            p => new { LastLoginDate = DateTime.Now, LastActivityDate = DateTime.Now, PasswordAttemps = 0, IsLockedOut = false },
                            p => p.Id == account.Id);

                        return true;
                    }
                    else
                    {
                        account.PasswordAttemps = (account.PasswordAttemps ?? 0) + 1;
                        if (account.PasswordAttemps == 1)
                        {
                            account.PasswordAttempStart = DateTime.Now;
                        }
                        if (account.PasswordAttemps >= maxInvalidPasswordAttempts)
                        {
                            account.IsLockedOut = true;
                            account.LastLockoutDate = DateTime.Now;
                        }

                        accountSet.Update(
                            p => new { 
                                PasswordAttemps = account.PasswordAttemps,
                                PasswordAttempStart = account.PasswordAttempStart,
                                IsLockedOut = account.IsLockedOut,
                                LastLockoutDate = account.LastLockoutDate
                            },
                            p => p.Id == account.Id);
                    }
                }
            }
            return false;
        }

        public bool ValidateUser(string username, string password)
        {
            UserAccountSet accountSet = new UserAccountSet();
            var result = accountSet.Any(
                p => p.IsApproved && !(bool)p.IsDisabled
                    && p.UserName == username && p.Password == password);
            return result;
        }

        public bool ChangePassword(string username, string newPassword, string changeBy)
        {
            UserAccountSet accountSet = new UserAccountSet();
            int count = accountSet.Update(p => new
            {
                Password = newPassword,
                ModifyBy = changeBy,
                ModifyTime = DateTime.Now
            }, p => p.UserName == username);

            return count > 0;
        }

        public bool ChangePasswordQuestionAndAnswer(string username, string newPasswordQuestion, string newPasswordAnswer, string changeBy)
        {
            UserAccountSet accountSet = new UserAccountSet();
            int count = accountSet.Update(p => new
            {
                PasswordQuestion = newPasswordQuestion,
                PasswordAnswer = newPasswordAnswer,
                ModifyBy = changeBy,
                ModifyTime = DateTime.Now
            }, p => p.UserName == username);

            return count > 0;
        }

        public string GetPassword(string username)
        {
            return GetUserByName(username, false).Password;
        }

        public bool ValidatePasswordAnswer(string username, string answer)
        {
            UserAccountSet accountSet = new UserAccountSet();
            var result = accountSet.Any(
                p => p.UserName == username && p.PasswordAnswer == answer);
            return result;
        }

        public void AddUserToApp(Guid userId, string appCode, string createBy)
        {
            UserAccountSet userAccountSet = new UserAccountSet();
            userAccountSet.AddUserToApp(userId, appCode, createBy);
        }
    }
}
